package cn.da.shuai.cool.mybatis.util;

import cn.da.shuai.cool.common.annotation.CoolField;
import cn.da.shuai.cool.common.entity.OrderBy;
import cn.da.shuai.cool.common.entity.UpdateBase;
import cn.da.shuai.cool.common.enums.SqlCond;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.enums.SqlKeyword;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class WrapperUtil {

    public static <E, S> IPage<E> page(IService<E> service, S param) {
        return page(service, param, service.getEntityClass());
    }

    public static <E, S> List<E> list(IService<E> service, S param) {
        return list(service, param, service.getEntityClass());
    }

    public static <E, T, S> QueryWrapper<E> getWrapper(IService<E> service, S param, Class<T> clazz) {
        QueryWrapper<E> wrapper = new QueryWrapper<>(service.getEntityClass());
        select(service, wrapper, clazz);
        condition(service, wrapper, param);
        return wrapper;
    }

    public static <E, T, S> List<T> list(IService<E> service, S param, Class<T> clazz) {
        QueryWrapper<E> wrapper = getWrapper(service, param, clazz);
        List<E> list = service.list(PageUtil.of(), wrapper);
        if (CollectionUtil.isEmpty(list)) {
            return ListUtil.empty();
        }
        return list.stream().map(e -> BeanUtil.toBean(e, clazz)).toList();
    }

    public static <E, T, S> T one(IService<E> service, S param, Class<T> clazz) {
        QueryWrapper<E> wrapper = getWrapper(service, param, clazz);
        E e = service.getOne(wrapper,false);
        if (e == null) {
            return null;
        }
        return BeanUtil.toBean(e, clazz);
    }

    public static <E, T, S> T oneById(IService<E> service, S id, Class<T> clazz) {
        UpdateBase<S> updateBase = new UpdateBase<>();
        updateBase.setId(id);
        return one(service, updateBase, clazz);
    }

    public static <E, S> E one(IService<E> service, S param) {
        return one(service, param, service.getEntityClass());
    }

    public static <E, S> boolean exists(IService<E> service, S id) {
        UpdateBase<S> updateBase = new UpdateBase<>();
        updateBase.setId(id);
        QueryWrapper<E> wrapper = getWrapper(service, updateBase, null);
        return service.exists(wrapper);
    }

    public static <E, T, S> IPage<T> page(IService<E> service, S param, Class<T> clazz) {
        return PageUtil.<T>of().setRecords(list(service, param, clazz));
    }

    public static <E, T> void condition(IService<E> service, QueryWrapper<E> wrapper, T condition) {
        if (condition == null || service == null || wrapper == null) {
            return;
        }

        if (condition instanceof Map<?, ?> map) {
            for (Map.Entry<?, ?> entry : map.entrySet()) {
                Object value = entry.getValue();
                if (value == null) {
                    continue;
                }
                if (value instanceof Iterator<?> || value instanceof Iterable<?>) {
                    wrapper.apply(entry.getKey() + " " + SqlKeyword.IN.getSqlSegment() + " (" + ArrayUtil.join(toArray(value), ",", WrapperUtil::getValue) + ")");
                } else {
                    wrapper.apply(entry.getKey() + " " + SqlKeyword.EQ.getSqlSegment() + " {0}", value);
                }
            }
            return;
        }

        Field[] fields = ReflectUtil.getFields(condition.getClass());
        for (Field field : fields) {
            CoolField coolField = field.getAnnotation(CoolField.class);
            String fieldName = getFieldName(field, coolField);
            if (StrUtil.isBlank(fieldName) || !ReflectUtil.hasField(service.getEntityClass(), fieldName)) {
                continue;
            }

            Object value = ReflectUtil.getFieldValue(condition, field);
            if (isNull(value)) {
                continue;
            }
            if (value instanceof Map<?, ?>) {
                condition(service, wrapper, (Map<?, ?>) value);
                continue;
            }
            if (coolField != null) {
                SqlCond[] cond = coolField.cond();
                for (SqlCond keyword : cond) {
                    switch (keyword) {
                        case IN, NOT_IN ->
                                wrapper.apply(fieldName + " " + keyword.getCond() + " (" + ArrayUtil.join(toArray(value), ",", WrapperUtil::getValue) + ")");
                        case BETWEEN, NOT_BETWEEN ->
                                wrapper.apply(fieldName + " " + keyword.getCond() + " {0} AND {1}", toArray(value));
                        case LIKE, NOT_LIKE ->
                                wrapper.apply(fieldName + " " + keyword.getCond() + " {0}", "%" + value + "%");
                        case IS_NULL, IS_NOT_NULL -> wrapper.apply(fieldName + " " + keyword.getCond());
                        case ORDER_BY -> orderBy(wrapper, value);
                        case GROUP_BY -> {
                            switch (value) {
                                case String[] strings -> wrapper.groupBy(ListUtil.toList(strings));
                                case Iterable<?> iterable -> iterable.forEach(o -> wrapper.groupBy(o.toString()));
                                case String s -> wrapper.groupBy(s);
                                default -> {}
                            }
                        }
                        default -> wrapper.apply(fieldName + " " + keyword.getCond() + " {0}", value);
                    }
                }
            } else {
                wrapper.eq(fieldName, value);
            }
        }
    }

    public static <E, T> void select(IService<E> service, QueryWrapper<E> wrapper, Class<T> clazz) {
        if (clazz == null || service == null || wrapper == null) {
            return;
        }
        wrapper.select(getFieldList(clazz, service.getEntityClass()));
    }

    private static <T, E> List<String> getFieldList(Class<T> clazz, Class<E> eClazz) {
        Field[] fields = ReflectUtil.getFields(clazz);
        List<String> fieldList = new ArrayList<>(fields.length);
        for (Field field : fields) {
            String fieldName = getFieldName(field);
            if (StrUtil.isBlank(fieldName) || "serial_version_UID".equals(fieldName)) {
                continue;
            }
            if (ReflectUtil.hasField(eClazz, field.getName())) {
                fieldList.add(fieldName);
            }
        }
        return fieldList;
    }

    private static String getFieldName(Field field) {
        CoolField coolField = field.getAnnotation(CoolField.class);
        return getFieldName(field, coolField);
    }

    private static String getFieldName(Field field, CoolField annotation) {
        String fieldName;
        if (annotation != null) {
            if (annotation.ignore()) {
                return null;
            }
            String value = annotation.value();

            if (StrUtil.isNotBlank(value)) {
                fieldName = value;
            } else {
                fieldName = StrUtil.toUnderlineCase(field.getName());
            }

        } else {
            fieldName = StrUtil.toUnderlineCase(field.getName());
        }
        return fieldName;
    }

    public static boolean isNull(Object value) {
        if (value == null) {
            return true;
        }
        if (value.getClass().isArray() && ArrayUtil.isEmpty(value)) {
            return true;
        }
        return switch (value) {
            case String s when StrUtil.isBlank(s) -> true;
            case Iterable<?> iterable when IterUtil.isEmpty(iterable) -> true;
            case Iterator<?> iterator when IterUtil.isEmpty(iterator) -> true;
            case Map<?, ?> map when CollectionUtil.isEmpty(map) -> true;
            default -> false;
        };
    }

    public static Object[] toArray(Object value) {
        if (value == null) {
            return new Object[0];
        }
        return switch (value) {
            case Iterable<?> iterable -> IterUtil.toList(iterable).toArray();
            case Iterator<?> iterator -> IterUtil.toList(iterator).toArray();
            default -> new Object[]{value};
        };
    }

    private static Object getValue(Object value) {
        if (value instanceof String) {
            return "'" + value + "'";
        }
        return value;
    }

    private static <E> void orderBy(QueryWrapper<E> wrapper, Object value) {
        switch (value) {
            case String[] strings -> wrapper.orderByDesc(ListUtil.toList(strings));
            case OrderBy[] orderBys -> {
                for (OrderBy orderBy : orderBys) {
                    orderBy(wrapper, orderBy);
                }
            }
            case Iterable<?> iterable -> iterable.forEach(v -> orderBy(wrapper, v));
            case OrderBy orderBy -> wrapper.orderBy(StrUtil.isNotBlank(orderBy.getName()), orderBy.getAsc(), orderBy.getName());
            case String s -> wrapper.orderByDesc(s);
            case null, default -> {}
        }
    }
}